home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Tech Arsenal 1
/
Tech Arsenal (Arsenal Computer).ISO
/
tek-02
/
gsdb25.zip
/
GS_DBNDX.PAS
< prev
next >
Wrap
Pascal/Delphi Source File
|
1991-08-01
|
63KB
|
1,387 lines
{
dBase III Index Handler
GS_DBNdx Copyright (c) Richard F. Griffin
15 November 1990
102 Molded Stone Pl
Warner Robins, GA 31088
-------------------------------------------------------------
This unit handles the objects for all dBase III index (.NDX)
operations.
changes:
16 Nov 90 - Modified KeyUpdate sub-procedure KeyInsert to
test for end-of-file during search for key.
22 Apr 91 - Modified SetMatchValue to be a method. This will
ensure consistency in building character and numeric
values. Also modified throughout to ensure the full
length was loaded into Ndx_Key_St for a field rather
than just moving length(Work_Key) characters.
Also added comments for KeyUpdate procedures.
02 May 91 - Added an IndexSignature constant so the GS_dBase unit
can confirm this unit is the dBase III index unit.
01 Aug 91 - Replaced string compare in DoMatchValue with call to
GS_Sort_Compare for speed increase.
}
{.pa}
{
┌─────────────────────┐
│ INTERFACE SECTION │
└─────────────────────┘
}
unit GS_DBNdx;
(*$N+,E+*) {Numeric coprocessor or emulation is}
{required to handle the double type}
{that dBase uses to store number and}
{date fields. If not using date or }
{numeric values, 16K of memory can}
{be avoided by deleting this and}
{changing double types to integer}
interface
uses
GS_Strng, {String handler routines}
GS_Sort, {Sort/Compare routine}
GS_Error, {Error handler routines}
GS_FileH; {File handler routines}
const
NdxBufSize = 16384;
IndexSignature = 'NDX3';
type
{
┌──────────────────────────────────────────────────────────┐
│ ******** Index Header Description ******** │
│ │
│ This record type describes the index file header. │
│ This is a 512-byte block that is located at the │
│ beginning of the index file. Refer to Appendix C │
│ for a description of the fields. │
└──────────────────────────────────────────────────────────┘
}
GS_Indx_Head = Record
Root : Longint;
Next_Blk : Longint;
Unknwn1 : Longint;
Key_Lgth : Integer;
Max_Keys : Integer;
Data_Typ : Integer;
Entry_Sz : Integer;
Unknwn2 : Longint;
Key_Form : array [0..487] of char;
end;
{
┌──────────────────────────────────────────────────────────┐
│ ******** Index Node Header Description ******** │
│ │
│ This record type describes the index file node header. │
│ Each node is a 512-byte block that is used as nodes │
│ to store keys and pointers. Refer to Appendix C │
│ for a description of the fields. │
└──────────────────────────────────────────────────────────┘
}
GS_Indx_Data = Record
Entry_Ct : Integer;
Unknwn1 : Integer;
Data_Ary : array [0..507] of byte;
{Memory array holding key entries}
Filler1 : array [0..255] of byte;
{Filler for possible overflow during}
{insert mode.}
end;
GS_Indx_EntPtr = ^GS_Indx_Etry; {Pointer of type GS_Indx_Etry. Will}
{be used to reference key entries }
{from GS_Indx_Data.Data_Ary.}
{
┌──────────────────────────────────────────────────────────┐
│ ******** Index Node Key Entry Description ******* │
│ │
│ This record type describes the index file key entries. │
│ Refer to Appendix C for a description of each field. │
└──────────────────────────────────────────────────────────┘
}
GS_Indx_Etry = Record
Block_Ax : Longint;
Recrd_Ax : Longint;
case Integer of
0 : (Char_Fld : array [1..255] of char);
1 : (Numb_Fld : double);
{dBase numeric and date fields are}
{stored as a floating point double}
end;
{
┌────────────────────────────────────────────────────────┐
│ Work table used to step through nodes. The previous │
│ nodes must be saved for finding the next or previous │
│ record during sequential reads. │
└────────────────────────────────────────────────────────┘
}
GS_Indx_Tabl = Record
Page_No : Longint; {Disk block holding node info}
Etry_No : Longint; {Last entry used in node}
Last_One : Longint; {Number of keys in this node }
Node_Pag : Boolean; {True for non-leaf nodes}
end;
GS_Indx_LPtr = ^GS_dBase_IX; {Pointer to object. Used by GS_dBase_DB}
{
┌─────────────────────────────────┐
│ GS_dBase_IX Object Definition │
└─────────────────────────────────┘
}
GS_dBase_IX = object
Ndx_Name : String[64]; {File name of index file}
Ndx_Hdr : GS_Indx_Head; {Index header information}
Ndx_File : file; {File type for index file}
Ndx_Tabl : array [0..25] of GS_Indx_Tabl;
{Array of 25 table entries to hold}
{the trail of non-leaf nodes that are}
{traversed during a key search. This }
{table is needed to track positions for}
{sequential reads (next and previous).}
Ndx_Lvl : integer; {Holds counter into Ndx_Tabl}
Ndx_Data : GS_Indx_Data; {Node header information}
Ndx_Pntr : GS_Indx_EntPtr; {Pointer to key entry information}
Ndx_Key_St : string[127]; {Holds last key value found on call to}
{either KeyRead or KeyFind}
Ndx_Key_Num : longint; {Holds last physical record number for a}
{key value found on call to either}
{KeyRead or KeyFind}
Ndx_Key_Form : string[127]; {Holds the key formula in type string}
KeyEOF : boolean; {True if last KeyRead attempted to read}
{beyond the range of index keys - either}
{beyond beginning or end of file}
ExactMatch : boolean; {Flag for type of test to use in KeyFind}
{It will force a match against an entire}
{key if true, and only for the length of}
{the passed argument if false. It is}
{initialized true.}
{
┌───────────────────────────────────────────────────────────────────────┐
│ *** These methods are described individually in the following *** │
│ pages. Their name describes their function. │
└───────────────────────────────────────────────────────────────────────┘
}
FUNCTION Init(IName : String) : boolean;
FUNCTION KeyFind(st : String) : longint;
FUNCTION KeyLocRec(rec : longint) : boolean;
FUNCTION KeyRead(a : LongInt) : longint;
PROCEDURE KeyUpdate (st : string; rec, crec : longint);
PROCEDURE Ndx_Close;
PROCEDURE Ndx_Get(blk : longint);
PROCEDURE Ndx_GetRecEntry;
PROCEDURE Ndx_GetRecPage(Ascnd : boolean);
FUNCTION Ndx_LastEntry : boolean;
PROCEDURE Ndx_Make(filname, formla : string; lth : integer; typ : char);
PROCEDURE Ndx_NodeData(pn, en, lo : longint; np : boolean);
PROCEDURE Ndx_Put(blk : longint);
Procedure KeyList(st : string);
FUNCTION SetMatchValue(st : string): string;
end;
{.pa}
{
┌──────────────────────────┐
│ IMPLEMENTATION SECTION │
└──────────────────────────┘
}
implementation
const
Next_Record = -1; {Token value passed to read next record}
Prev_Record = -2; {Token value passed to read previous record}
Top_Record = -3; {Token value passed to read first record}
Bttm_Record = -4; {Token value passed to read final record}
ValueHigh = 1; {Token value passed for key comparison high}
ValueLow = -1; {Token value passed for key comparison low}
ValueEqual = 0; {Token value passed for key comparison equal}
var
Work_Key : string; {Holds key passed in Find and KeyUpdate}
Work_Num : Double; {Holds numeric value of Work_Key if needed}
RPag : Longint; {Work variable to hold current index block}
RNum : Longint; {Work variable for record number}
IsAscend : Boolean; {Flag for ascending/descending status.}
{Set based on Next/Previous Record read}
{.pa}
{
Ndx_Make
╔══════════════════════════════════════════════════════════════════╗
║ ║
║ The Ndx_Make method will create an index file ║
║ ║
║ Calling the Method: ║
║ ║
║ objectname.Ndx_Make(filname, formla, lth, typ) ║
║ ║
║ ( where objectname is of type GS_dBase_IX ║
║ filename is of type string ║
║ formla is of type string) ║
║ lth is of type integer for key length ║
║ typ is of type char for field type ║
║ ║
║ Result: ║
║ ║
║ The index file is created. ║
║ ║
╚══════════════════════════════════════════════════════════════════╝
}
Procedure GS_dBase_IX.Ndx_Make(filname, formla : string; lth : integer;
typ : char);
begin
Ndx_Name := filname+'.NDX'; {Setup file name}
GS_FileAssign(Ndx_File,Ndx_Name,NdxBufSize);
GS_FileRewrite(Ndx_File,1);
FillChar(Ndx_Hdr, SizeOf(Ndx_Hdr),#0);
Ndx_Hdr.Root := 1;
Ndx_Hdr.Next_Blk := 2;
case typ of
'N',
'D' : begin
Ndx_Hdr.Data_Typ := 1;
lth := 8;
end;
else Ndx_Hdr.Data_Typ := 0;
end;
Ndx_Hdr.Key_Lgth := lth;
Ndx_Hdr.Max_Keys := (SizeOf(Ndx_Hdr)-4) div (lth+8);
Ndx_Hdr.Entry_Sz := lth+8;
CnvStrToAsc(formla,Ndx_Hdr.Key_Form, length(formla)+1);
move(Ndx_Hdr, Ndx_Data, SizeOf(Ndx_Hdr));
Ndx_Put(0);
FillChar(Ndx_Data, SizeOf(Ndx_Data),#0);
Ndx_Put(1);
end;
{.pa}
{
INIT
╔══════════════════════════════════════════════════════════════════╗
║ ║
║ The INIT method initializes objectname by reading the .NDX ║
║ file and loading file structure information into the object. ║
║ ║
║ Calling the Method: ║
║ ║
║ oldindex := objectname.Init(String) ║
║ ║
║ ( where oldindex is of type boolean, ║
║ objectname is of type GS_dBase_IX, ║
║ String is the file name of the dBase ║
║ file (without the .NDX extension). ║
║ ║
║ Result: ║
║ ║
║ Index file object is initialized. ║
║ True will be returned if file exists. ║
║ ║
╚══════════════════════════════════════════════════════════════════╝
┌──────────────────────────────────────────┐
│ The INIT method will do the following: │
│ 1. Open the index file │
│ 2. Read the first block (header) │
│ into objectname. │
│ 3. Set Ndx_Lvl to zero, which will │
│ indicate no reads performed. │
│ 4. Return flag (false if new file) │
└──────────────────────────────────────────┘
}
function GS_dBase_IX.Init(IName : String) : boolean;
var
i : integer;
begin
Ndx_Name := IName + '.NDX';
if GS_FileExists(Ndx_File, Ndx_Name) then
begin
GS_FileAssign(Ndx_File,Ndx_Name,NdxBufSize);
GS_FileReset(Ndx_File,1);
Init := true;
end
else
begin
ShowError(2,Ndx_Name);
Init := false; {return a flag showing no file}
end;
Ndx_Get(0); {Read first block of file for header info}
{Note that no error checking is done }
{in this version }
move(Ndx_Data, Ndx_Hdr, 512); {Store in header info area}
Ndx_Lvl := 0; {Initialize the node step table}
Ndx_Tabl[0].Page_No := 0;
Ndx_Tabl[0].Etry_No := 0;
Ndx_Tabl[0].Last_One := 0;
KeyEOF := false; {Initialize EOF Flag to false}
ExactMatch := true; {Initialize to use an exact match test}
{
┌──────────────────────────────────────────┐
│ This portion of code will extract the │
│ "formula", which is usually the field │
│ that is used for indexing. However, it │
│ can be compound (FLDA+FLDB). The │
│ formula is placed in a string for use │
│ during index updates. │
└──────────────────────────────────────────┘
}
move(Ndx_Hdr.Key_Form[0], Ndx_Key_Form[1],100);
i := 1;
while Ndx_Key_Form[i] <> #0 do inc(i);
Ndx_Key_Form[0] := chr(pred(i));
Ndx_Key_Form := TrimR(Ndx_Key_Form);
Ndx_Key_Form := TrimL(Ndx_Key_Form);
end;
{.pa}
{
┌─────────────────────────────────────┐
│ This routine sets up the match │
│ string. It sets the length of the │
│ match for full or partial, and │
│ converts to numeric if needed. │
└─────────────────────────────────────┘
}
function GS_dBase_IX.SetMatchValue(st : string): string;
var
rl : integer;
begin
if Ndx_Hdr.Data_Typ = 0 then
begin {if a character key field then --}
FillChar(Work_Key[1], SizeOf(Work_Key), ' '); {Fill with blanks}
Work_Key := st;
if ExactMatch then
Work_Key[0] := chr(Ndx_Hdr.Key_Lgth);
end
else
begin
val(st,Work_Num,rl);
if rl <> 0 then ShowError(501,st);
move(Work_Num, Work_Key[1], 8);
Work_Key[0] := #8;
end;
SetMatchValue := Work_Key;
end;
{.pa}
{
KEYFIND
╔══════════════════════════════════════════════════════════════════╗
║ ║
║ The KeyFind method will return the physical record location ║
║ of the record matching the key value passed as the argument. ║
║ ExactMatch controls the length of the match check. If ║
║ ExactMatch is true, the entire key in the .NDX entry must ║
║ match the value passed. If false, the check will only be ║
║ for the length of the string passed. ║
║ ║
║ Calling the Method: ║
║ ║
║ longintvalu := objectname.KeyFind(string) ║
║ ║
║ ( where objectname is of type GS_dBase_IX, ║
║ string is a value used to search the ║
║ .NDX file looking for a match. ║
║ ║
║ Result: ║
║ ║
║ 1. longintvalu will point to the physical record, ║
║ or will be zero if no match. ║
║ 2. Ndx_Key_St will contain the key value. ║
║ 3. Ndx_Key_Num will contain the record number. ║
║ ║
╚══════════════════════════════════════════════════════════════════╝
}
function GS_dBase_IX.KeyFind(st : string) : LongInt;
var
i : integer; {Work variable}
rl : integer; {Result code for Val procedure}
ct : integer; {Variable to hold BlockRead byte count}
Less_Than : boolean; {Flag to hunt for key match}
Loop_Cnt : longint;
Match_Cnd : integer;
procedure StoreMatchValue;
begin
move(Ndx_Pntr^.Char_Fld,Ndx_Key_St[1],Ndx_Hdr.Key_Lgth);
{Move the key field to Ndx_Key_St.}
Ndx_Key_St[0] := Work_Key[0]; {Now insert the length into Ndx_Key_St}
end;
function DoMatchValue : integer;
var
nks : double;
begin
if Ndx_Hdr.Data_Typ = 0 then {Character key field}
Match_Cnd := GS_Sort_Compare(Ndx_Key_St, Work_Key)
else {Numeric key field}
begin
move(Ndx_Key_St[1],nks,8);
if nks > Work_Num then Match_Cnd := ValueHigh
else if nks = Work_Num then Match_Cnd := ValueEqual
else Match_Cnd := ValueLow;
end;
DoMatchValue := Match_Cnd;
end;
begin
KeyEOF := false; {Reset End-of-File to false}
Ndx_Key_Num := 0; {Initialize}
Ndx_Key_St := ''; {Initialize}
Ndx_Lvl := 0; {Initialize index level}
Work_Key := SetMatchValue(st); {Set key comparison value}
RPag := Ndx_Hdr.Root; {Get root node address}
while RPag <> 0 do {While a non-leaf node, do this}
begin
Ndx_Get(RPag); {Get Node using RPag as block number}
Ndx_Pntr := Addr(Ndx_Data.Data_Ary[0]);
{Get pointer to first entry}
Loop_Cnt := Ndx_Pntr^.Block_Ax; {Get the next node pointer to see if it}
{is zero, meaning a leaf node}
i := 0; {Initialize i as counter}
Less_Than := Ndx_Data.Entry_Ct > 0;
{Start out with less than flag true}
{Will be false if Entry Count is 0}
{which means an empty node}
while (less_than) and (i <= Ndx_Data.Entry_Ct) do
{Hunt for a match. If i = last entry in}
{the node, the last entry is used for}
{the next node search}
begin
Ndx_Pntr := Addr(Ndx_Data.Data_Ary[i * Ndx_Hdr.Entry_Sz]);
{Get pointer to entry indexed by i}
inc(i); {Increment the counter}
StoreMatchValue; {Put the key value in Ndx_Key_St for}
{matching}
Less_Than := DoMatchValue = ValueLow;
{Test looking for greater or equal than}
{the key value. Less_Than will be set}
{false when found, setting the condition}
{to leave this portion of the routine}
end;
{
┌──────────────────────────────────────────┐
│ Save the node data for this node as: │
│ 1. Block Number from RPag. │
│ 2. Entry number of match or last one. │
│ 3. Set total number of entries. This │
│ is entry count+1 for non-leaf nodes │
│ 4. Set non-leaf flag to true. │
└──────────────────────────────────────────┘
}
Ndx_NodeData(RPag,i,Ndx_Data.Entry_Ct+1,true);
if Loop_Cnt = 0 then RPag := 0
else RPag := Ndx_Pntr^.Block_Ax;
{Get the next node in the tree}
end;
Ndx_Tabl[Ndx_Lvl].Node_Pag := false;
{Set non-leaf flag to false for this}
{last level}
dec(Ndx_Tabl[Ndx_Lvl].Last_One);
{Set total number of entries to the }
{correct value for a leaf node}
if Ndx_Data.Entry_Ct = 0 then
begin
KeyFind := 0;
exit;
end;
if (DoMatchValue <> ValueEqual) or
(Ndx_Tabl[Ndx_Lvl].Last_One < Ndx_Tabl[Ndx_Lvl].Etry_No)
then Ndx_Key_Num := 0 {if unable to find a match, the above}
{routine would have stopped when a}
{greater key was found, or would have}
{continued to Last_One. Since the entry}
{count is one less for leaf nodes, even}
{if there was a match at Last_one, it is}
{not valid, and was only a coincidence.}
{In either case, set record number = 0.}
else
Ndx_Key_Num := Ndx_Pntr^.Recrd_Ax;
{When there is a match with the key,}
{get the physical record number}
KeyFind := Ndx_Key_Num; {Return with the record number}
end;
{.pa}
{
KEYLOCREC
╔══════════════════════════════════════════════════════════════════╗
║ ║
║ The KeyLocRec method will search the .NDX file to find the ║
║ matching index entry pointing to the physical record location ║
║ of the record requested. ║
║ ║
║ Calling the Method: ║
║ ║
║ flag := objectname.KeyLocRec(key, position) ║
║ ║
║ ( where objectname is of type GS_dBase_IX, ║
║ key is the key string ║
║ position is the physical record number ║
║ of the matching .DBF record.) ║
║ ║
║ Result: ║
║ ║
║ Boolean True is returned if a match is found. ║
║ The current index entry will be set to the record ║
║ if a match does exist. ║
║ ║
╚══════════════════════════════════════════════════════════════════╝
}
Function GS_dBase_IX.KeyLocRec (rec : longint) : boolean;
var
lr : longint;
begin
if rec = Ndx_Key_Num then
begin {Exit if already at the record}
KeyLocRec := true;
exit;
end;
lr := KeyRead(Top_Record);
while (not KeyEOF) and (lr <> rec) do lr := KeyRead(Next_Record);
if (KeyEOF) then KeyLocRec := false
else KeyLocRec := true;
end;
{.pa}
{
KEYREAD
╔══════════════════════════════════════════════════════════════════╗
║ ║
║ The KeyRead method will return the physical record location ║
║ of the record requested. The only options that may be asked ║
║ for are Top, Bottom, Next, and Previous. ║
║ ║
║ Calling the Method: ║
║ ║
║ longintvalu := objectname.KeyRead(position) ║
║ ║
║ ( where objectname is of type GS_dBase_IX, ║
║ position is in -1 to -4, ║
║ longintvalu is physical record number ║
║ of the matching .DBF record. ║
║ ║
║ Result: ║
║ ║
║ longintvalu will point to the physical record. ║
║ ║
╚══════════════════════════════════════════════════════════════════╝
}
FUNCTION GS_dBase_IX.KeyRead(a : longint) : longint;
var
N_L_Hold : Integer; {Tempory variable for index level}
ct : Integer; {Work variable for Blockread count}
{
┌───────────────────────────────────────────────┐
│ Start of KeyRead function. This will │
│ accomplish the following: │
│ │
│ 1. If first time for index, set any call │
│ for a Next or Previous read to a Top │
│ read command. │
│ 2. Use case select for Top/Bttm/Next/Prev. │
│ Return physical .DBF record in RNum. │
│ 3. If not a valid action, set RNum to 0. │
│ 4. Move key value to Ndx_Key_St. │
│ 5. Move RNum to Ndx_Key_Num. │
│ 6. Return RNum value to calling procedure. │
└───────────────────────────────────────────────┘
}
{ Start of KeyRead }
begin
RNum := a; {Get action command}
if ((a = Next_Record) or (a = Prev_Record)) and
(Ndx_Lvl = 0) then RNum := Top_Record;
{if first time through, use Top_Record}
{command instead}
KeyEOF := false; {End-of-File initially set false}
case RNum of {Select KeyRead Action}
Next_Record : begin
IsAscend := true;
{Will be an ascending read}
N_L_Hold := Ndx_Lvl;
{Save old index level}
{
┌─────────────────────────────────────┐
│ If the last record read was the │
│ last entry in the node, you have │
│ to step back through the index │
│ levels to find the next node. │
└─────────────────────────────────────┘
}
if Ndx_LastEntry then
{If last entry in node already used,}
{go find the next node}
begin
while (Ndx_LastEntry) and (Ndx_Lvl > 0) do
dec(Ndx_Lvl);
{Step back through the levels until you}
{find a good one, or run out of levels.}
if Ndx_Lvl = 0 then
{if out of levels, process for EOF}
begin
Ndx_Lvl := N_L_Hold;
{Get old level number to restore}
KeyEOF := true;
{Set End-of-File true}
end else
begin {Otherwise, get next entry data}
inc(Ndx_Tabl[Ndx_Lvl].Etry_No);
{Step to next Entry Number}
Ndx_GetRecEntry;
{Go search for next good record}
end;
end
else inc(Ndx_Tabl[Ndx_Lvl].Etry_No);
{Otherwise, just step to next entry}
Ndx_Pntr :=
Addr(Ndx_Data.Data_Ary[(
(Ndx_Tabl[Ndx_Lvl].Etry_No - 1) *
Ndx_Hdr.Entry_Sz)]);
{Get pointer to the key entry}
RNum := Ndx_Pntr^.Recrd_Ax;
{Get record number for the key entry}
end;
Prev_Record : begin
IsAscend := false;
{Will be a descending read}
N_L_Hold := Ndx_Lvl;
{Save old index level}
{
┌─────────────────────────────────────┐
│ If the last record read was the │
│ first entry in the node, you have │
│ to step back through the index │
│ levels to find the next node. │
└─────────────────────────────────────┘
}
if Ndx_Tabl[Ndx_Lvl].Etry_No = 1 then
{If last entry in node already used,}
{go find the next node}
begin
while (Ndx_Tabl[Ndx_Lvl].Etry_No = 1) and
(Ndx_Lvl > 0) do
dec(Ndx_Lvl);
{Step back through the levels until you}
{find a good one, or run out of levels.}
if Ndx_Lvl = 0 then
{if out of levels, process for EOF}
begin
Ndx_Lvl := N_L_Hold;
{Get old level number to restore}
KeyEOF := true;
{Set End-of-File true}
end else
begin {Otherwise, get next entry data}
dec(Ndx_Tabl[Ndx_Lvl].Etry_No);
{Step to next Entry Number}
Ndx_GetRecEntry;
{Go search for next good record}
end;
end
else dec(Ndx_Tabl[Ndx_Lvl].Etry_No);
{Otherwise, just step to next entry}
Ndx_Pntr :=
Addr(Ndx_Data.Data_Ary[(
(Ndx_Tabl[Ndx_Lvl].Etry_No - 1) *
Ndx_Hdr.Entry_Sz)]);
{Get pointer to the key entry}
RNum := Ndx_Pntr^.Recrd_Ax;
{Get record number for the key entry}
end;
Top_Record,
Bttm_Record : begin
IsAscend := Top_Record = RNum;
{Ascending search if Top, otherwise}
{descending. An ascending search will}
{return the first index key as the Top.}
{A descending search will return the}
{last index key as the 'Top'}
Ndx_Lvl := 0; {Clear index levels for new stack}
RPag := Ndx_Hdr.Root;
{Get root node address}
Ndx_GetRecPage(IsAscend);
{Go get valid record}
end;
else RNum := 0; {If no valid action, return zero}
end;
move(Ndx_Pntr^.Char_Fld,Ndx_Key_St[1],Ndx_Hdr.Key_Lgth);
{Move the key field to Ndx_Key_St.}
{The Move procedure must be used since}
{Char_Fld is not a true Pascal string.}
Ndx_Key_St[0] := chr(Ndx_Hdr.Key_Lgth);
{Now insert the length into Ndx_Key_St}
{so it is a valid string we can use}
Ndx_Key_Num := RNum; {Save RNum in Ndx_Key_Num}
KeyRead := RNum; {Return RNum}
end;
{.pa}
{
NDX_CLOSE
╔══════════════════════════════════════════════════════════════════╗
║ ║
║ The Ndx_Close method will close the index file from this ║
║ object. ║
║ ║
║ Calling the Method: ║
║ ║
║ objectname.Ndx_Close ║
║ ║
║ ( where objectname is of type GS_dBase_IX ║
║ ║
║ Result: ║
║ ║
║ The index file is closed. ║
║ ║
╚══════════════════════════════════════════════════════════════════╝
}
Procedure GS_dBase_IX.Ndx_Close;
begin
GS_FileClose(Ndx_File);
end;
{.pa}
{
NDX_GET
╔══════════════════════════════════════════════════════════════════╗
║ ║
║ The Ndx_Get method will read a block from the index file for ║
║ this object. ║
║ ║
║ Calling the Method: ║
║ ║
║ objectname.Ndx_Get(Blk) ║
║ ║
║ ( where objectname is of type GS_dBase_IX ║
║ blk is longint number of block to read) ║
║ ║
║ Result: ║
║ ║
║ The index block (node) is read into Ndx_Data ║
║ ║
╚══════════════════════════════════════════════════════════════════╝
}
Procedure GS_dBase_IX.Ndx_Get(blk : longint);
var
r : word;
begin
GS_FileRead(Ndx_File,blk*512,Ndx_Data,512,r);
if r < 512 then ShowError(100,'Ndx_Get');
end;
Procedure GS_dBase_IX.Ndx_NodeData(pn, en, lo : longint; np : boolean);
begin
inc(Ndx_Lvl); {Prepare to store node information as}
{part of the Ndx_Lvl hierarchy}
with Ndx_Tabl[Ndx_Lvl] do {Use the index level entry}
begin
Page_No := pn; {Save Block number}
Etry_No := en; {Set entry number}
Last_One := lo; {Set total number of entries.}
Node_Pag := np; {Set non-leaf flag}
end;
end;
{
┌─────────────────────────────────────┐
│ This procedure will locate the │
│ starting page to search for an │
│ entry. It selects the entry │
│ number contained at the present │
│ index level and passes its node │
│ pointer to Get_PageRec. This is │
│ needed to read the index blocks in │
│ the correct sequence. │
└─────────────────────────────────────┘
}
procedure GS_dBase_IX.Ndx_GetRecEntry;
begin
RPag := Ndx_Tabl[Ndx_Lvl].Page_No;
{Get page number for this index level}
Ndx_Get(RPag); {Get Node using RPag as block number}
Ndx_Pntr := Addr(Ndx_Data.Data_Ary[(Ndx_Tabl[Ndx_Lvl].Etry_No- 1)
* Ndx_Hdr.Entry_Sz]);
{Get pointer to key entry (relative zero)}
RPag := Ndx_Pntr^.Block_Ax; {Get Next node number in RPag}
Ndx_GetRecPage(IsAscend); {Go get the next record from a non-leaf}
{node. Pass the argument for either an}
{ascending or descending search}
end;
{
┌─────────────────────────────────────┐
│ This procedure will step the nodes │
│ until it finds a leaf node. The │
│ starting node is contained in the │
│ variable RPag; the record number │
│ of the first or last key (based) │
│ on Ascnd) will be placed in RNum. │
└─────────────────────────────────────┘
}
procedure GS_dBase_IX.Ndx_GetRecPage(Ascnd : boolean);
var
ec : integer; {Work variable for entry count}
begin
while RPag <> 0 do {Next node number in RPag will be zero}
{when taken from a leaf node.}
begin
Ndx_Get(RPag); {Get Node using RPag as block number}
Ndx_NodeData(RPag,0,Ndx_Data.Entry_Ct+1,true);
{Store Node data}
{
┌───────────────────────────────────────────────┐
│ This portion of code checks to see if called │
│ by Next/Top or Bttm/Prev, and sets the entry │
│ to 1 or last node entry, based on Ascnd │
└───────────────────────────────────────────────┘
}
if Ascnd then
begin
ec := 0; {Set ec = first entry (relative zero)}
Ndx_Tabl[Ndx_Lvl].Etry_No := 1;
{Set Entry Number in table to first one}
end else
begin
ec := Ndx_Data.Entry_Ct; {Set ec to last entry (relative zero)}
{Note there are Entry_Ct+1 entries for}
{non-leaf nodes. It will be adjusted}
{later if it is a leaf node}
Ndx_Tabl[Ndx_Lvl].Etry_No := ec+1;
{Set Entry Number in table to last one}
end;
Ndx_Pntr := Addr(Ndx_Data.Data_Ary[ec * Ndx_Hdr.Entry_Sz]);
{Get pointer to correct entry in node}
RPag := Ndx_Pntr^.Block_Ax; {Get Next node number in RPag}
end;
{
┌───────────────────────────────────────────────┐
│ This portion of code checks to see if the │
│ index file is empty. If so, the EOF is set │
│ and the routine is quit. │
└───────────────────────────────────────────────┘
}
if Ndx_Data.Entry_Ct = 0 then
begin
KeyEOF := true;
RNum := 0;
exit;
end;
Ndx_Tabl[Ndx_Lvl].Node_Pag := false;
{Set non-leaf flag to false for leaf}
if not Ascnd then
begin
dec(Ndx_Tabl[Ndx_Lvl].Etry_No);
{Set Entry Number in table to last one}
{for a non-leaf node}
Ndx_Pntr := Addr(Ndx_Data.Data_Ary[ec-1 * Ndx_Hdr.Entry_Sz]);
{Get pointer to correct leaf entry for}
{the last entry in the node}
end;
Ndx_Tabl[Ndx_Lvl].Node_Pag := false;
{Set non-leaf flag to false for this}
{last level}
dec(Ndx_Tabl[Ndx_Lvl].Last_One); {Set total number of entries to the }
{correct value for a leaf node}
RNum := Ndx_Pntr^.Recrd_Ax; {Get the physical record number for}
{the first key entry}
end;
{
┌───────────────────────────────────────────────┐
│ This function will return true if all │
│ entries have been processed in the │
│ Ndx_Lvl layer number passed to the function │
└───────────────────────────────────────────────┘
}
function GS_dBase_IX.Ndx_LastEntry : boolean;
begin
if Ndx_Tabl[Ndx_Lvl].Etry_No = Ndx_Tabl[Ndx_Lvl].Last_One then
Ndx_LastEntry := true else Ndx_LastEntry := false;
end;
{.pa}
{
NDX_PUT
╔══════════════════════════════════════════════════════════════════╗
║ ║
║ The Ndx_Put method will write a block from the index file for ║
║ this object. ║
║ ║
║ Calling the Method: ║
║ ║
║ objectname.Ndx_Put(Blk) ║
║ ║
║ ( where objectname is of type GS_dBase_IX ║
║ blk is longint number of block to write) ║
║ ║
║ Result: ║
║ ║
║ The index block (node) is written from Ndx_Data ║
║ ║
╚══════════════════════════════════════════════════════════════════╝
}
Procedure GS_dBase_IX.Ndx_Put(blk : longint);
var
r : word;
begin
GS_FileWrite(Ndx_File,blk*512,Ndx_Data,512,r);
if r < 512 then ShowError(101,'Ndx_Put');
end;
Procedure GS_dBase_IX.KeyUpdate (st : string; rec, crec : longint);
var
ct : integer;
nu_key : longint;
em_hold : boolean; {holds ExactMatch flag during this}
t_num : double;
lr,
b1,
b2 : longint;
rlst,
e1,
e2,
n1,
n2 : integer;
s1,
s2 : string[127];
r1 : GS_Indx_Data;
{
This routine deletes the current entry by overlaying the remaining entries
over the entry location, and then decrementing the entry count
}
Procedure DeleteEntry;
begin
with Ndx_Tabl[Ndx_Lvl] do
begin
move(Ndx_Data.Data_Ary[(Etry_No)*Ndx_Hdr.Entry_Sz],
Ndx_Data.Data_Ary[(Etry_No-1)*Ndx_Hdr.Entry_Sz],
Ndx_Hdr.Entry_Sz*(Last_One-Etry_No));
dec(Last_One);
dec(Ndx_Data.Entry_Ct);
end;
end;
{ This routine inserts an entry by making room in the current data array
and inserting the new entry. The entry count is then incremented.
}
Procedure InsertEntry;
begin
with Ndx_Tabl[Ndx_Lvl] do
begin
if (Etry_No <> 0) and (not KeyEOF) then
begin {If at a valid entry number and not}
{at EOF, make room for the entry. }
move(Ndx_Data.Data_Ary[(Etry_No-1)*Ndx_Hdr.Entry_Sz],
Ndx_Data.Data_Ary[(Etry_No)*Ndx_Hdr.Entry_Sz],
Ndx_Hdr.Entry_Sz*(((Last_One-Etry_No)+1)));
Ndx_Pntr := Addr(Ndx_Data.Data_Ary[(Etry_No-1) * Ndx_Hdr.Entry_Sz]);
end
else
begin {else put entry at end of array}
Ndx_Pntr := Addr(Ndx_Data.Data_Ary[Etry_No*Ndx_Hdr.Entry_Sz]);
inc(Etry_No);
end;
inc(Last_One); {account for additional entry}
inc(Ndx_Data.Entry_Ct); {account for additional entry}
move(Work_Key[1],Ndx_Pntr^.Char_Fld,Ndx_Hdr.Key_Lgth)
{Move the key field from Work_Key.}
{The Move procedure must be used since}
{Char_Fld is not a true Pascal string.}
end;
end;
{ This routine searches back through the nodes to replace the key value in
the non-leaf node.
}
procedure ReplacePointerEntry;
begin
while (Ndx_LastEntry) and (Ndx_Lvl > 0) do dec(Ndx_Lvl);
{Search for entry that requires the key}
{value. Value is not needed for the }
{last entry in a non-leaf node. Thus, }
{this searches until it finds a pointer}
{that is not the last entry in a node, }
{or until the root node is reached. }
if Ndx_Lvl > 0 then
begin {Replace key value with new one if not }
{the last entry in the root node. }
Ndx_Get(Ndx_Tabl[Ndx_Lvl].Page_No);
{Get the correct index node.}
Ndx_Pntr := Addr(Ndx_Data.Data_Ary
[(Ndx_Tabl[Ndx_Lvl].Etry_No-1) * Ndx_Hdr.Entry_Sz]);
{Get entry that pointed to the leaf node}
move(Ndx_Key_St[1],Ndx_Pntr^.Char_Fld,Ndx_Hdr.Key_Lgth);
{Move the key field from Ndx_Key_St.}
Ndx_Put(Ndx_Tabl[Ndx_Lvl].Page_No);
{Write updated node to disk}
end;
end;
{ This routine is used to delete all references to a record key. It will
delete the key from the leaf node, and then search the non-leaf node and
replace the pointer if it was the last entry in the non-leaf node.
}
Procedure KeyDelete;
begin
DeleteEntry; {delete the key from this node.}
Ndx_Put(Ndx_Tabl[Ndx_Lvl].Page_No);
{write the updated node.}
if Ndx_Tabl[Ndx_Lvl].Last_One = 0 then
begin {if this was the only entry, then }
{go delete any previous references}
{to the node. }
dec(Ndx_Lvl);
if Ndx_Lvl > 0 then
begin {this will be recursive until it }
{steps past the root node. }
Ndx_Get(Ndx_Tabl[Ndx_Lvl].Page_No);
{Get the node.}
KeyDelete; {and delete the pointer.}
end;
exit; {leave this procedure when all the}
{references are deleted. }
end;
if Ndx_Tabl[Ndx_Lvl].Etry_No > Ndx_Tabl[Ndx_Lvl].Last_One then
begin {if this was the last entry in the node,}
{make sure non-leaf node pointers use }
{the predecessor key value. }
Ndx_Pntr := Addr(Ndx_Data.Data_Ary
[(Ndx_Tabl[Ndx_Lvl].Last_One-1) * Ndx_Hdr.Entry_Sz]);
{point to the predecessor entry.}
move(Ndx_Pntr^.Char_Fld,Ndx_Key_St[1],Ndx_Hdr.Key_Lgth);
{Move the key field to Ndx_Key_St.}
{The Move procedure must be used since}
{Char_Fld is not a true Pascal string.}
Ndx_Key_St[0] := chr(length(Work_Key));
{Now insert the length into Ndx_Key_St}
{so it is a valid string we can use}
dec(Ndx_Lvl);
if Ndx_Lvl > 0 then ReplacePointerEntry;
{replace the node pointer with this new key}
end;
end;
{ This routine will divide a block into two equal blocks and then store the
index levels (n1 and n2), entry counts (e1 and e2), and block numbers
(b1 and b2) for later node pointer updates. The new key (from the middle
of the block's entries) will be saved in s1.
}
Procedure SplitBlock;
begin
b1 := Ndx_Hdr.Next_Blk; {Get the next available block.}
inc(Ndx_Hdr.Next_Blk); {Update the next available block.}
Ndx_NodeData(b1,1,Ndx_Tabl[Ndx_Lvl].Last_One,Ndx_Tabl[Ndx_Lvl].Node_Pag);
{make a new index table entry}
with Ndx_Tabl[Ndx_Lvl] do
begin {put the first half of the block in the}
{new block. Adjust the entry and last }
{one counts accordingly. }
n1 := Ndx_Lvl;
Ndx_Data.Entry_Ct := Last_One div 2;
{Number of entries in first half.}
e2 := Last_One - Ndx_Data.Entry_Ct;
{Number of entries in second half.}
Last_One := Ndx_Data.Entry_Ct;
e1 := Last_One;
if Node_Pag then dec(Ndx_Data.Entry_Ct);
Ndx_Pntr := Addr(Ndx_Data.Data_Ary
[(Ndx_Tabl[Ndx_Lvl].Last_One-1) * Ndx_Hdr.Entry_Sz]);
move(Ndx_Pntr^.Char_Fld,s1[1],Ndx_Hdr.Key_Lgth);
s1[0] := chr(Ndx_Hdr.Key_Lgth);
{Save the last key entry in the block.}
Ndx_Put(Page_No); {Save the block.}
end;
dec(Ndx_Lvl);
with Ndx_Tabl[Ndx_Lvl] do
begin
b2 := Page_No;
n2 := Ndx_Lvl;
Last_One := e2;
Ndx_Data.Entry_Ct := e2;
if Node_Pag then dec(Ndx_Data.Entry_Ct);
move(Ndx_Data.Data_Ary[e1*Ndx_Hdr.Entry_Sz],
Ndx_Data.Data_Ary[0],Ndx_Hdr.Entry_Sz*(e2));
{Shift second half to beginning of the}
{buffer array.}
Ndx_Put(Page_No); {Save the block}
move(Ndx_Hdr, Ndx_Data, 512);
{Store from header info area}
Ndx_Put(0);
dec(Ndx_Lvl); {Step back to previous node.}
end;
end;
{ This routine is used to create a new root node when the split block
pointers will not fit in the current root node.
}
Procedure MakeRootNode;
begin
Ndx_Lvl := 0;
with Ndx_Tabl[Ndx_Lvl] do
begin
Page_No := Ndx_Hdr.Next_Blk; {Get next available block.}
inc(Ndx_Hdr.Next_Blk); {Increment the next available block.}
Ndx_Hdr.Root := Page_No; {Set root pointer to this block.}
move(Ndx_Hdr, Ndx_Data, 512);
{Store from header info area}
Ndx_Put(0); {Write updated header block.}
Ndx_Pntr := Addr(Ndx_Data.Data_Ary[0]);
Ndx_Data.Entry_Ct := 0;
Ndx_Pntr^.Recrd_Ax := 0;
Ndx_Pntr^.Block_Ax := b2;
Last_One := 1;
Etry_No := 1;
Ndx_Put(Page_No);
end;
end;
{ This routine will split the current node, create a new root node if needed,
and then insert the newly created block in the proper sequence in the node.
}
procedure ExpandIndex;
var
kEOF : boolean;
begin
SplitBlock;
if Ndx_Lvl = 0 then MakeRootNode;
Work_Key := s1;
Ndx_Get(Ndx_Tabl[Ndx_Lvl].Page_No);
{Get the proper non-leaf node}
kEOF := KeyEOF;
KeyEOF := false; {temporarily turn off EOF flag}
InsertEntry;
KeyEOF := kEOF;
Ndx_Pntr^.Recrd_Ax := 0;
Ndx_Pntr^.Block_Ax := b1;
if Ndx_Tabl[Ndx_Lvl].Last_One <= Ndx_Hdr.Max_Keys then
{test to see if more entries than the}
{maximum allowed. }
begin {write the block if below the max. }
Ndx_Put(Ndx_Tabl[Ndx_Lvl].Page_No);
end else
begin
ExpandIndex; {Keep expanding recursively as long as}
{is necessary. }
end;
end;
{ This routine will insert the new key into the index. It will search for
matching keys and insert the new key after any existing matches. It will
then check to see if the node is filled, and split the block if necessary.
}
Procedure KeyInsert;
begin
nu_key := KeyFind(st); {Find a matching key.}
if nu_key <> 0 then {If there is a match, continue looking}
begin {until no more matches. }
if Ndx_Hdr.Data_Typ = 0 then
{Search for character string keys}
while (Ndx_Key_St = Work_Key) and (not KeyEOF) do
nu_key := KeyRead(Next_Record)
else
begin {Search for numeric and date keys}
move(Ndx_Key_St[1],t_num,8);
while (t_num = Work_Num) and (not KeyEOF) do
nu_key := KeyRead(Next_Record);
end;
end;
InsertEntry; {Insert the key here}
Ndx_Pntr^.Recrd_Ax := rec;
Ndx_Pntr^.Block_Ax := 0;
if Ndx_Tabl[Ndx_Lvl].Etry_No > Ndx_Tabl[Ndx_Lvl].Last_One then
{See if this is the last entry in the }
{leaf node. If so, go replace the old}
{pointer in the non-leaf node. }
begin
r1 := Ndx_Data;
n1 := Ndx_Lvl;
Ndx_Key_St := Work_Key;
ReplacePointerEntry;
Ndx_Lvl := n1;
Ndx_Data := r1;
end;
if Ndx_Tabl[Ndx_Lvl].Last_One <= Ndx_Hdr.Max_Keys then
{if fewer than the maximum number of key}
{entries allowed, write the updated node}
begin
Ndx_Put(Ndx_Tabl[Ndx_Lvl].Page_No);
end else
begin
ExpandIndex; {otherwise, split the block.}
end;
end;
begin
Work_Key := SetMatchValue(st); {Set key comparison value}
if rec = crec then {Tests for Append vs Update}
begin
if Work_Key = Ndx_Key_St then exit;
KeyDelete;
end;
em_hold := ExactMatch;
ExactMatch := true;
KeyInsert;
ExactMatch := em_hold;
if crec < 0 then exit;
lr := KeyFind(st);
while lr <> rec do lr := KeyRead(Next_Record);
end;
Procedure GS_dBase_IX.KeyList(st : string);
var
ofil : text;
RPag : LongInt;
Lst_One,
i,j,k,v : integer;
rl : integer;
ct : integer;
recnode,
Less_Than : boolean;
begin
assign(ofil, st);
ReWrite(ofil);
with Ndx_Hdr do
begin
writeln(ofil,'--------------------------------------------------');
writeln(ofil,'':8,Ndx_Key_St);
writeln(ofil,'Root =',Root:3,' Next Block Available:',Next_Blk:3);
end;
RPag := 1;
while RPag <> Ndx_Hdr.Next_Blk do
begin
Seek(Ndx_File,RPag*512);
BlockRead(Ndx_File,Ndx_Data,512,ct);
Lst_One := Ndx_Data.Entry_Ct+1;
write(ofil,RPag:2,' [',Ndx_Data.Entry_Ct,']');
Ndx_Pntr := Addr(Ndx_Data.Data_Ary[0]);
recnode := Ndx_Pntr^.Block_Ax = 0;
k := Lst_One;
if recnode then dec(k);
v := 1;
i := 1;
while (i <= k) do
begin
Ndx_Pntr := Addr(Ndx_Data.Data_Ary[((i-1) * Ndx_Hdr.Entry_Sz)]);
with Ndx_Pntr^ do
begin
write(ofil,'':v,Block_Ax:5);
v := 9;
if i = Lst_One then write(ofil,' 0 - empty')
else
begin
write(ofil,Recrd_Ax:5,' ');
if Ndx_Hdr.Data_Typ <> 0 then
write(ofil,Numb_Fld)
else
for j := 1 to Ndx_Hdr.Key_Lgth do
write(ofil,Char_Fld[j]);
end;
WRITELN(OFIL);
end;
inc(i);
end;
writeln(ofil);
inc(RPag);
end;
System.Close(ofil);
end;
end.